﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using System.Runtime.Serialization;
using Microsoft.Crm.SdkTypeProxy;
using System.Globalization;

namespace XrmLinq.Entities
{
    /// <summary>
    /// Represents a CRM Entity.
    /// </summary>
    /// <remarks>
    /// The <c>CrmEntity</c> abstract class can be used to simplify data access and reduce the amount of code that you have to write to convert between CRM types and .NET types.
    /// </remarks>
    /// <example>
    /// <code>
    /// <![CDATA[
    /// [CrmEntityAttribute(SchemaName = "account", PrimaryKey = "accountid")]
    /// public partial class Account : CrmEntity
    /// {
    ///     public Account() : base() { }
    ///     public Account(DynamicEntity entity) : base(entity) { }
    /// 
    ///     public class Fields
    ///     {
    ///         public const string SchemaName = "account";
    ///         public const string AccountId = "accountid";
    ///     }
    /// 
    ///     [CrmFieldAttribute(SchemaName = "accountid")]
    ///     public virtual Guid AccountId { get; set; }
    ///     [CrmFieldAttribute(SchemaName = "name")]
    ///     public virtual string AccountName { get; set; }
    /// 
    ///     private List<EMail> _accountEmails;
    ///     [CrmRelationshipAttribute(FromEntity = "account", ToEntity = "email", FromKey = "accountid", ToKey = "regardingobjectid")]
    ///     public virtual List<EMail> Emails
    ///     {
    ///         get 
    ///         { 
    ///             if (_accountEmails == null && this.AccountId != Guid.Empty) { _accountEmails = CrmDataAccess.RetrieveOneToMany<EMail>(this.Sdk, XrmLinq.EMail.Fields.SchemaName, "regardingobjectid", this.AccountId);
    ///             return _accountEmails; 
    ///         }
    ///     }
    /// 
    ///     private Contact _n1_accountPrimaryContact;
    ///     [CrmRelationshipAttribute(FromEntity = "account", ToEntity = "contact", FromKey = "primarycontactid", ToKey = "contactid")]
    ///     public virtual Contact PrimaryContact
    ///     {
    ///         get
    ///         {
    ///             if ((_n1_accountPrimaryContact == null && this.PrimaryContactId.HasValue) && this.PrimaryContactId != Guid.Empty) { _n1_accountPrimaryContact = CrmDataAccess.Retrieve<Contact>(this.Sdk, XrmLinq.Contact.Fields.SchemaName, "contactid", this.PrimaryContactId.Value);
    ///             return _n1_accountPrimaryContact;
    ///         }
    ///     }
    /// 
    ///     protected override void Initialize()
    ///     {
    ///         this.AccountId = GetPrimaryKey(Fields.AccountId);
    ///         this.AccountName = GetString(Fields.AccountName);
    ///     }
    ///     
    ///     public override DynamicEntity ToDynamic()
    ///     {
    ///         DynamicEntity entity = new DynamicEntity(Fields.SchemaName);
    ///         PropertyCollection properties = new PropertyCollection();
    ///     
    ///         SetPrimaryKey(properties, Fields.AccountId, this.AccountId);
    ///         SetString(properties, Fields.AccountName, this.AccountName);
    ///     }
    /// }
    /// ]]>
    /// </code>
    /// </example>
    public abstract class CrmEntity
    {
        protected CrmService _sdk;
        protected ICrmService _isdk;
        /// <summary>
        /// CRM sdk web service, this can be an IFD instance or inhouse instance.
        /// <para>
        /// LINQ library and the Mapper will automatically manage assigning a correct <c>CrmService</c> instance to each entity. This is very important, if you fail to provide a value to this property all the lazy loaded properties will fail to load.
        /// </para>
        /// </summary>
        protected CrmService Sdk
        {
            get { return _sdk; }
            set { _sdk = value; }
        }

        protected ICrmService ISdk
        {
            get { return _isdk; }
            set { _isdk = value; }
        }

        private DynamicEntity _entity;
        /// <summary>
        /// DynamicEntity representing an instance of a CRM record.
        /// </summary>
        protected DynamicEntity Entity
        {
            get { return _entity; }
            set { _entity = value; }
        }

        /// <summary>
        /// Default constructor, if you are creating a new instance of a class use this constructor, if you are retrieving data using a DynamicEntity use the overloaded constructor. It will automatically map CRM types to .NET types.
        /// </summary>
        protected CrmEntity() { }

        /// <summary>
        /// Overloaded constructor. This is used internally by the LINQ library. We use <c>RetrieveDynamic</c> requests, by passing the DynamicEntity to this constructor it will automaticall map CRM types to .NET types.
        /// </summary>
        /// <param name="entity"></param>
        protected CrmEntity(DynamicEntity entity)
        {
            Initialize(null, null, entity);
        }

        internal void Initialize(CrmService sdk, DynamicEntity entity)
        {
            Initialize(sdk, null, entity);
        }

        internal void Initialize(ICrmService sdk, DynamicEntity entity)
        {
            Initialize(null, sdk, entity);
        }

        internal void Initialize(CrmService sdk, ICrmService isdk, DynamicEntity entity)
        {
            _sdk = sdk;
            _isdk = isdk;
            _entity = entity;

            Initialize();
        }

        /// <summary>
        /// Called by the overloaded constructor. By calling this method you will convert all the properties from the <c>DynamicEntity</c> to <c>.NET types</c>.
        /// </summary>
        protected abstract void Initialize();

        /// <summary>
        /// Converts all .NET types of this class to a DynamicEntity instance that the CRM webservice can understand.
        /// </summary>
        /// <returns>A DynamicEntity instance.</returns>
        public abstract DynamicEntity ToDynamic();

        /// <summary>
        /// <c>UniqueIdentifier</c> to <c>Guid</c>.
        /// </summary>
        /// <param name="field">Schema name of the uniqueidentifier field.</param>
        /// <returns>A guid.</returns>
        protected Guid GetUniqueIdentifier(string field)
        {
            UniqueIdentifier property = ConvertTo<UniqueIdentifier>(field);
            return (property != null) ? property.Value : Guid.Empty;
        }

        /// <summary>
        /// <c>Lookup</c> to <c>Guid</c>.
        /// </summary>
        /// <param name="field">Schema name of the lookup field.</param>
        /// <returns>A guid.</returns>
        protected Guid GetLookup(string field)
        {
            Lookup property = ConvertTo<Lookup>(field);
            return (property != null) ? property.Value : Guid.Empty;
        }

        /// <summary>
        /// <c>CrmDateTime</c> to <c>DateTime?</c>.
        /// </summary>
        /// <param name="field">Schema name of the datetime field.</param>
        /// <returns>A nullable datetime.</returns>
        protected DateTime? GetDateTime(string field)
        {
            CrmDateTime property = ConvertTo<CrmDateTime>(field);
            if ((property != null) && !string.IsNullOrEmpty(property.Value))
            {
                return DateTime.Parse(property.Value, CultureInfo.CurrentCulture);
            }
            return null;
        }

        /// <summary>
        /// <c>string</c> to <c>string</c>.
        /// </summary>
        /// <param name="field">Schema name of the string field.</param>
        /// <returns>A string.</returns>
        protected string GetString(string field)
        {
            return ConvertTo<string>(field);
        }

        /// <summary>
        /// <c>CrmMoney</c> to a <c>decimal?</c>.
        /// </summary>
        /// <param name="field">Schema name of the money field.</param>
        /// <returns>A nullable decimal.</returns>
        protected decimal? GetMoney(string field)
        {
            CrmMoney property = ConvertTo<CrmMoney>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>CrmDecimal</c> to <c>decimal?</c>.
        /// </summary>
        /// <param name="field">Schema name of the decimal field.</param>
        /// <returns>A nullable decimal.</returns>
        protected decimal? GetDecimal(string field)
        {
            CrmDecimal property = ConvertTo<CrmDecimal>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>CrmFloat</c> to <c>double?</c>.
        /// </summary>
        /// <param name="field">Schema name of the float field.</param>
        /// <returns>A nullable double.</returns>
        protected double? GetFloat(string field)
        {
            CrmFloat property = ConvertTo<CrmFloat>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>Picklist</c> to <c>int?</c>.
        /// </summary>
        /// <param name="field">Schema name of the picklist field.</param>
        /// <returns>A nullable integer.</returns>
        protected int? GetPicklist(string field)
        {
            Picklist property = ConvertTo<Picklist>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>Picklist</c> to the specified type <c>T</c>.
        /// </summary>
        /// <param name="field">Schema name of the picklist field.</param>
        /// <returns>T</returns>
        protected T GetPicklist<T>(string field)
        {
            Picklist property = ConvertTo<Picklist>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return default(T);
            }
            return (T)Enum.ToObject(typeof(T), property.Value);
        }

        /// <summary>
        /// <c>Key</c> to <c>Guid</c>.
        /// </summary>
        /// <param name="field">Schema name of the key field.</param>
        /// <returns>A guid.</returns>
        protected Guid GetPrimaryKey(string field)
        {
            Key property = ConvertTo<Key>(field);
            if (property != null)
            {
                return property.Value;
            }
            return Guid.Empty;
        }

        /// <summary>
        /// <c>Status</c> to <c>int</c>.
        /// </summary>
        /// <param name="field">Schema name of the status field.</param>
        /// <returns>An integer.</returns>
        protected int GetStatus(string field)
        {
            Status property = ConvertTo<Status>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return 1;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>CrmBoolean</c> to <c>bool?</c>.
        /// </summary>
        /// <param name="field">Schema name of the boolean field.</param>
        /// <returns>A nullable boolean.</returns>
        protected bool? GetBoolean(string field)
        {
            CrmBoolean property = ConvertTo<CrmBoolean>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>CrmNumber</c> to <c>int?</c>.
        /// </summary>
        /// <param name="field">Schema name of the number field.</param>
        /// <returns>A nullable integer.</returns>
        protected int? GetInteger(string field)
        {
            CrmNumber property = ConvertTo<CrmNumber>(field);
            if ((property == null) || (property.IsNull || property.IsNullSpecified))
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>State</c> to <c>string</c>.
        /// </summary>
        /// <param name="field">Schema name of the state field.</param>
        /// <returns>A string.</returns>
        protected string GetState(string field)
        {
            return ConvertTo<string>(field);
        }

        /// <summary>
        /// <c>Owner</c> to <c>Guid?</c>.
        /// </summary>
        /// <param name="field">Schema name of the owner field.</param>
        /// <returns>A guid.</returns>
        protected Guid? GetOwner(string field)
        {
            Owner property = ConvertTo<Owner>(field);
            if ((property == null) || property.Value == null)
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>Memo</c> to <c>string</c>.
        /// </summary>
        /// <param name="field">Schema name of the memo field.</param>
        /// <returns>A string.</returns>
        protected string GetMemo(string field)
        {
            return ConvertTo<string>(field);
        }

        /// <summary>
        /// <c>Not Supported Yet</c>.
        /// </summary>
        /// <param name="field">Schema name of the partylist field.</param>
        /// <returns>null</returns>
        protected static DynamicEntity[] GetPartyList(string field)
        {
            return new DynamicEntity[] { };
        }

        /// <summary>
        /// <c>Customer</c> to <c>Guid?</c>.
        /// </summary>
        /// <param name="field">Schema name of the customer field.</param>
        /// <returns>A nullable guid.</returns>
        protected Guid? GetCustomer(string field)
        {
            Customer property = ConvertTo<Customer>(field);
            if ((property == null) || property.Value == null)
            {
                return null;
            }
            return property.Value;
        }

        /// <summary>
        /// <c>string</c> to <c>StringProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetString(PropertyCollection properties, string field, string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                properties.Add(new StringProperty(field, value));
            }
        }

        /// <summary>
        /// <c>Not Supported Yet</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetStatus(PropertyCollection properties, string field, int value)
        {
            // use setstaterequest
        }

        /// <summary>
        /// <c>Guid?</c> to <c>LookupProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetLookup(PropertyCollection properties, string entity, string field, Guid? value)
        {
            if (value.HasValue && value.Value != Guid.Empty)
            {
                properties.Add(new LookupProperty(field, new Lookup(entity, value.Value)));
            }
        }

        /// <summary>
        /// <c>string</c> to <c>StringProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetMemo(PropertyCollection properties, string field, string value)
        {
            if (!string.IsNullOrEmpty(value))
            {
                properties.Add(new StringProperty(field, value));
            }
        }

        /// <summary>
        /// <c>bool?</c> to <c>CrmBooleanProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetBoolean(PropertyCollection properties, string field, bool? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmBooleanProperty(field, new CrmBoolean(value.Value)));
            }
        }

        /// <summary>
        /// <c>decimal?</c> to <c>CrmMoneyProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetMoney(PropertyCollection properties, string field, decimal? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmMoneyProperty(field, new CrmMoney(value.Value)));
            }
        }

        /// <summary>
        /// <c>decimal?</c> to <c>CrmDecimalProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetDecimal(PropertyCollection properties, string field, decimal? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmDecimalProperty(field, new CrmDecimal(value.Value)));
            }
        }

        /// <summary>
        /// <c>int?</c> to <c>CrmNumberProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetInteger(PropertyCollection properties, string field, int? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmNumberProperty(field, new CrmNumber(value.Value)));
            }
        }

        /// <summary>
        /// <c>double?</c> to <c>CrmFloatProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetFloat(PropertyCollection properties, string field, double? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmFloatProperty(field, new CrmFloat(value.Value)));
            }
        }

        /// <summary>
        /// <c>Guid</c> to <c>KeyProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected void SetPrimaryKey(PropertyCollection properties, string field, Guid value)
        {
            if (value != Guid.Empty)
            {
                properties.Add(new KeyProperty(field, new Key(value)));
            }
        }

        /// <summary>
        /// <c>int?</c> to <c>PicklistProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetPicklist(PropertyCollection properties, string field, int? value)
        {
            if (value.HasValue)
            {
                if (value.Value != 0)
                {
                    properties.Add(new PicklistProperty(field, new Picklist(value.Value)));
                }
            }
        }

        /// <summary>
        /// <c>Not Supported Yet</c>.
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="field"></param>
        /// <param name="value"></param>
        protected static void SetPartyList(PropertyCollection properties, string field, DynamicEntity[] value)
        {
        }

        /// <summary>
        /// <c>Not Supported Yet.</c>
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="entity"></param>
        /// <param name="field"></param>
        /// <param name="value"></param>
        protected static void SetPartyList(PropertyCollection properties, string entity, string field, DynamicEntity[] value)
        {
        }

        /// <summary>
        /// <c>DateTime?</c> to <c>CrmDateTimeProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetDateTime(PropertyCollection properties, string field, DateTime? value)
        {
            if (value.HasValue)
            {
                properties.Add(new CrmDateTimeProperty(field, new CrmDateTime(value.Value.ToString("s", CultureInfo.CurrentCulture))));
            }
        }

        /// <summary>
        /// <c>Not Supported Yet.</c>
        /// </summary>
        /// <param name="properties"></param>
        /// <param name="entity"></param>
        /// <param name="field"></param>
        /// <param name="value"></param>
        protected static void SetCustomer(PropertyCollection properties, string entity, string field, Guid? value)
        {
            if (value.HasValue && value.Value != Guid.Empty)
            {

            }
        }

        /// <summary>
        /// <c>Guid?</c> to <c>UniqueIdentifierProperty</c>.
        /// </summary>
        /// <param name="properties">PropertyCollection holder which can be used by a <c>DynamicEntity</c>.</param>
        /// <param name="field">Schema name of the field.</param>
        /// <param name="value">Value of the field.</param>
        protected static void SetUniqueIdentifier(PropertyCollection properties, string field, Guid? value)
        {
            if (value.HasValue && value.Value != Guid.Empty)
            {
                properties.Add(new UniqueIdentifierProperty(field, new UniqueIdentifier(value.Value)));
            }
        }

        /// <summary>
        /// Converts a crm field that's within a DynamicEntity to the specified type T.
        /// <c>Internal Method</c>.
        /// </summary>
        /// <typeparam name="T">CRM data type.</typeparam>
        /// <param name="field">Schema name of the field.</param>
        /// <returns>The specified CRM data type.</returns>
        protected T ConvertTo<T>(string field)
        {
            if (_entity.Properties.Contains(field))
            {
                return ((T)_entity.Properties[field]);
            }
            return default(T);
        }

        /// <summary>
        /// Helper method.
        /// <c>Internal Method</c>.
        /// </summary>
        /// <param name="properties">Instance of a PropertyCollection.</param>
        /// <param name="p">Property that you want to add to the collection.</param>
        protected static void AddToProperties(PropertyCollection properties, Property p)
        {
            if (p != null)
            {
                properties.Add(p);
            }
        }
    }
}
